package com.dragon.common.core.crypto.symmetric;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * AES加密算法实现<br>
 * 高级加密标准（英语：Advanced Encryption Standard，缩写：AES），在密码学中又称Rijndael加密法<br>
 * 对于Java中AES的默认模式是：AES/ECB/PKCS5Padding，如果使用CryptoJS，请调整为：padding: CryptoJS.pad.Pkcs7
 *
 * <p>
 * 相关概念说明：
 * <pre>
 * mode:    加密算法模式，是用来描述加密算法（此处特指分组密码，不包括流密码，）在加密时对明文分组的模式，它代表了不同的分组方式
 * padding: 补码方式是在分组密码中，当明文长度不是分组长度的整数倍时，需要在最后一个分组中填充一些数据使其凑满一个分组的长度。
 * iv:      在对明文分组加密时，会将明文分组与前一个密文分组进行XOR运算（即异或运算），但是加密第一个明文分组时不存在“前一个密文分组”，
 *          因此需要事先准备一个与分组长度相等的比特序列来代替，这个比特序列就是偏移量。
 * </pre>
 * <p>
 * 相关概念见：<a href="https://blog.csdn.net/OrangeJack/article/details/82913804">...</a>
 *
 * @author dragon
 * @version 1.0
 * @date 2024/8/1 10:35
 */
public class AES {

    private static final Logger log = LoggerFactory.getLogger(AES.class);

    /**
     * 电码本模式
     */
    public final static String ECB_NoPadding = "AES/ECB/NoPadding";
    public final static String ECB_ISO10126Padding = "AES/ECB/ISO10126Padding";
    public final static String ECB_PKCS5Padding = "AES/ECB/PKCS5Padding";
    /**
     * 密码分组链接模式
     */
    public final static String CBC_NoPadding = "AES/CBC/NoPadding";
    public final static String CBC_ISO10126Padding = "AES/CBC/ISO10126Padding";
    public final static String CBC_PKCS5Padding = "AES/CBC/PKCS5Padding";
    /**
     * 计算器模式
     */
    public final static String CTR_NoPadding = "AES/CTR/NoPadding";
    /**
     * 密码反馈模式
     */
    public final static String CFB_NoPadding = "AES/CFB/NoPadding";
    public final static String CFB_ISO10126Padding = "AES/CFB/ISO10126Padding";
    public final static String CFB_PKCS5Padding = "AES/CFB/PKCS5Padding";
    /**
     * 输出反馈模式
     */
    public final static String OFB_NoPadding = "AES/OFB/NoPadding";
    public final static String OFB_ISO10126Padding = "AES/OFB/ISO10126Padding";
    public final static String OFB_PKCS5Padding = "AES/OFB/PKCS5Padding";
    /**
     * 密码电文失窃
     */
    public final static String CTS_NoPadding = "AES/CTS/NoPadding";
    /**
     * 传播密码块
     */
    public final static String PCBC_NoPadding = "AES/PCBC/NoPadding";
    public final static String PCBC_ISO10126Padding = "AES/PCBC/ISO10126Padding";
    public final static String PCBC_PKCS5Padding = "AES/PCBC/PKCS5Padding";

    /**
     * 算法名称/加密模式/填充方式
     */
    private final String algorithmModePadding;
    /**
     * 密钥
     */
    private final byte[] key;
    /**
     * 偏移向量
     */
    private final byte[] iv;


    public AES(String algorithmModePadding, String key) {
        this(algorithmModePadding, key.getBytes(StandardCharsets.UTF_8), null);
    }

    public AES(String algorithmModePadding, String key, String iv) {
        this(algorithmModePadding, key.getBytes(StandardCharsets.UTF_8), iv.getBytes(StandardCharsets.UTF_8));
    }

    public AES(String algorithmModePadding, byte[] key, byte[] iv) {
        this.algorithmModePadding = algorithmModePadding;
        this.key = key;
        this.iv = iv;
    }

    /**
     * 加密
     *
     * @param data 明文
     * @return 密文
     */
    public String encrypt(String data) {
        return encrypt(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 加密
     *
     * @param data 明文
     * @return 密文
     */
    public String encrypt(byte[] data) {
        try {
            // 创建AES加密器
            Cipher cipher = Cipher.getInstance(algorithmModePadding);

            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            if (ArrayUtils.isEmpty(iv)) {
                cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            } else {
                IvParameterSpec ivSpec = new IvParameterSpec(iv);
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            }

            // 加密字节数组
            byte[] ciphertext = cipher.doFinal(data);

            return Base64.getEncoder().encodeToString(ciphertext);
        } catch (Exception e) {
            log.error("AES加密失败 {}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 解密
     *
     * @param data 密文
     * @return 明文
     */
    public String decrypt(String data) {
        return decrypt(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 解密
     *
     * @param data 密文
     * @return 明文
     */
    public String decrypt(byte[] data) {
        try {
            // 创建AES加密器
            Cipher cipher = Cipher.getInstance(algorithmModePadding);

            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            if (ArrayUtils.isEmpty(iv)) {
                cipher.init(Cipher.DECRYPT_MODE, keySpec);
            } else {
                IvParameterSpec ivSpec = new IvParameterSpec(iv);
                cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            }

            byte[] ciphertext = Base64.getDecoder().decode(data);

            // 解密字节数组
            byte[] plaintextBytes = cipher.doFinal(ciphertext);

            return new String(plaintextBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("AES解密失败 {}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

}
